{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 日期时间运算和比较\n",
    "\n",
    "本教程介绍Pandas中日期时间的算术运算、比较操作和筛选方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 核心概念\n",
    "\n",
    "在Pandas中进行日期时间运算和比较，主要用到以下工具：\n",
    "\n",
    "- **`pd.Timestamp`**: 表示单个时间点，类似于Python的`datetime`\n",
    "- **`pd.Timedelta`**: 表示固定长度的时间间隔（如5天、3小时）\n",
    "- **`pd.DateOffset`**: 表示日历间隔（如1个月、1年），会考虑月份和年份的实际天数\n",
    "- **时间序列索引**: 使用日期时间作为索引的Series或DataFrame\n",
    "\n",
    "**Timedelta vs DateOffset的区别：**\n",
    "- `Timedelta`：固定时间长度，如1天=24小时，不受月份影响\n",
    "- `DateOffset`：日历时间，如1个月可能28-31天，1年可能365-366天\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from datetime import datetime, timedelta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 基本算术运算\n",
    "\n",
    "Pandas支持对日期时间进行加减运算：\n",
    "\n",
    "**Timedelta常用参数：**\n",
    "- `days`: 天数\n",
    "- `hours`: 小时数\n",
    "- `minutes`: 分钟数\n",
    "- `seconds`: 秒数\n",
    "- `weeks`: 周数\n",
    "- `milliseconds`, `microseconds`, `nanoseconds`: 更小的时间单位\n",
    "\n",
    "**DateOffset常用参数：**\n",
    "- `years`: 年数\n",
    "- `months`: 月数\n",
    "- `days`: 天数\n",
    "- `hours`, `minutes`, `seconds` 等\n",
    "\n",
    "**注意：**\n",
    "- 使用`Timedelta`处理月份和年份时，不会考虑月份天数的变化（如2月）\n",
    "- 使用`DateOffset`处理月份和年份时，会正确处理月份天数的差异\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 日期时间算术运算"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 时间差计算\n",
    "\n",
    "两个日期时间相减可以得到一个`Timedelta`对象，常用属性：\n",
    "\n",
    "- `.days`: 总天数（整数）\n",
    "- `.seconds`: 总秒数（整数，不包括天数）\n",
    "- `.total_seconds()`: 总秒数（浮点数，包括天数）\n",
    "- `.components`: 分解为天、时、分、秒等组件\n",
    "\n",
    "**应用场景：**\n",
    "- 计算两个日期之间的间隔\n",
    "- 计算年龄、工龄等\n",
    "- 计算项目周期\n",
    "- 计算数据的时间跨度\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 基本算术运算\n",
    "date = pd.Timestamp('2023-06-15')\n",
    "print(f\"原始日期: {date}\")\n",
    "\n",
    "# 加法运算\n",
    "print(\"\\n加法运算:\")\n",
    "print(f\"加1天: {date + pd.Timedelta(days=1)}\")\n",
    "print(f\"加1周: {date + pd.Timedelta(weeks=1)}\")\n",
    "print(f\"加1个月: {date + pd.DateOffset(months=1)}\")\n",
    "print(f\"加1年: {date + pd.DateOffset(years=1)}\")\n",
    "\n",
    "# 减法运算\n",
    "print(\"\\n减法运算:\")\n",
    "print(f\"减3天: {date - pd.Timedelta(days=3)}\")\n",
    "print(f\"减2周: {date - pd.Timedelta(weeks=2)}\")\n",
    "print(f\"减6个月: {date - pd.DateOffset(months=6)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 时间序列运算\n",
    "\n",
    "对时间序列进行运算时，可以对索引（日期时间）进行操作：\n",
    "\n",
    "**常用操作：**\n",
    "1. **索引平移**: 对整个时间序列的索引加上或减去时间差\n",
    "2. **计算相邻日期差**: 使用`diff()`方法计算相邻时间点的间隔\n",
    "3. **时间索引转换**: 将日期时间索引转换为其他格式\n",
    "\n",
    "**注意：**\n",
    "- 直接对索引进行运算会创建一个新的索引\n",
    "- 使用`to_series()`可以将DatetimeIndex转换为Series，方便使用Series的方法\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 时间差计算\n",
    "start_date = pd.Timestamp('2023-01-01')\n",
    "end_date = pd.Timestamp('2023-12-31')\n",
    "\n",
    "time_diff = end_date - start_date\n",
    "print(f\"开始日期: {start_date}\")\n",
    "print(f\"结束日期: {end_date}\")\n",
    "print(f\"时间差: {time_diff}\")\n",
    "print(f\"天数: {time_diff.days}\")\n",
    "print(f\"总秒数: {time_diff.total_seconds()}\")\n",
    "\n",
    "# 计算年龄\n",
    "birth_date = pd.Timestamp('1990-05-15')\n",
    "today = pd.Timestamp.now()\n",
    "age = today - birth_date\n",
    "print(f\"\\n出生日期: {birth_date.date()}\")\n",
    "print(f\"今天: {today.date()}\")\n",
    "print(f\"年龄: {age.days // 365}岁 {age.days % 365}天\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 日期时间比较\n",
    "\n",
    "Pandas支持标准的比较操作符（`<`, `>`, `==`, `<=`, `>=`, `!=`）：\n",
    "\n",
    "**比较特性：**\n",
    "- 可以直接比较两个`Timestamp`对象\n",
    "- 可以与字符串日期比较（Pandas会自动转换）\n",
    "- 可以与`datetime`对象比较\n",
    "- 比较结果返回布尔值\n",
    "\n",
    "**时间序列索引比较：**\n",
    "- 对DatetimeIndex进行比较会返回布尔数组\n",
    "- 可以用于条件筛选，提取特定时间范围的数据\n",
    "\n",
    "**使用场景：**\n",
    "- 筛选特定日期之前或之后的数据\n",
    "- 找出特定时间段的记录\n",
    "- 按时间条件过滤数据\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. 时间序列运算"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 日期范围筛选\n",
    "\n",
    "Pandas提供了多种方式筛选特定日期范围的数据：\n",
    "\n",
    "**常用日期时间属性：**\n",
    "- `.year`: 年份\n",
    "- `.month`: 月份（1-12）\n",
    "- `.day`: 日期（1-31）\n",
    "- `.dayofweek`: 星期几（0=周一，6=周日）\n",
    "- `.quarter`: 季度（1-4）\n",
    "- `.weekofyear`: 一年中的第几周\n",
    "- `.dayofyear`: 一年中的第几天\n",
    "- `.is_leap_year`: 是否为闰年\n",
    "\n",
    "**筛选方法：**\n",
    "1. **使用属性筛选**: `ts[ts.index.month == 1]` - 筛选1月份\n",
    "2. **使用between方法**: 筛选指定日期范围\n",
    "3. **使用query方法**: 对DataFrame使用SQL风格的查询\n",
    "\n",
    "**组合条件：**\n",
    "- 使用`&`（与）、`|`（或）、`~`（非）组合多个条件\n",
    "- 注意：每个条件需要用括号括起来\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建时间序列\n",
    "dates = pd.date_range('2023-01-01', periods=10, freq='D')\n",
    "ts = pd.Series(range(10), index=dates)\n",
    "print(\"原始时间序列:\")\n",
    "print(ts)\n",
    "\n",
    "# 整个序列加时间差\n",
    "shifted_index = ts.index + pd.Timedelta(days=5)\n",
    "ts_shifted = pd.Series(ts.values, index=shifted_index)\n",
    "print(\"\\n索引整体后移5天:\")\n",
    "print(ts_shifted)\n",
    "\n",
    "# 计算相邻日期差\n",
    "date_diffs = ts.index.to_series().diff()\n",
    "print(\"\\n相邻日期差:\")\n",
    "print(date_diffs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用between和query方法\n",
    "\n",
    "**between方法：**\n",
    "- 语法：`series.between(left, right, inclusive='both')`\n",
    "- `inclusive`: 控制边界包含方式\n",
    "  - `'both'`: 两边都包含（默认）\n",
    "  - `'left'`: 只包含左边\n",
    "  - `'right'`: 只包含右边\n",
    "  - `'neither'`: 两边都不包含\n",
    "\n",
    "**query方法：**\n",
    "- 适用于DataFrame，使用字符串表达式\n",
    "- 可以引用列名和索引\n",
    "- 支持复杂的逻辑表达式\n",
    "- 比布尔索引更简洁易读\n",
    "\n",
    "**性能提示：**\n",
    "- 对于大数据集，直接使用布尔索引可能比`query`更快\n",
    "- 对于复杂条件，`query`方法可读性更好\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. 日期时间比较"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 复杂的时间条件筛选\n",
    "\n",
    "在实际数据分析中，经常需要根据复杂的时间模式筛选数据：\n",
    "\n",
    "**常用时间模式：**\n",
    "- 特定日期：`ts.index.day == 15` - 每月15号\n",
    "- 特定星期：`ts.index.dayofweek == 4` - 周五\n",
    "- 月末：需要结合`to_period()`和`end_time`\n",
    "- 月初：`ts.index.day == 1`\n",
    "- 季度末：需要结合季度属性\n",
    "\n",
    "**to_period方法：**\n",
    "- 将日期时间转换为周期对象\n",
    "- 常用频率：'D'（日）、'M'（月）、'Q'（季度）、'Y'（年）\n",
    "- `end_time`: 获取周期的结束时间\n",
    "- `normalize()`: 将时间标准化为当天的00:00:00\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 基本比较操作\n",
    "date1 = pd.Timestamp('2023-06-15')\n",
    "date2 = pd.Timestamp('2023-06-20')\n",
    "\n",
    "print(f\"日期1: {date1}\")\n",
    "print(f\"日期2: {date2}\")\n",
    "print(f\"\\n比较结果:\")\n",
    "print(f\"date1 < date2: {date1 < date2}\")\n",
    "print(f\"date1 > date2: {date1 > date2}\")\n",
    "print(f\"date1 == date2: {date1 == date2}\")\n",
    "print(f\"date1 <= date2: {date1 <= date2}\")\n",
    "print(f\"date1 >= date2: {date1 >= date2}\")\n",
    "\n",
    "# 与字符串比较\n",
    "print(f\"\\n与字符串比较:\")\n",
    "print(f\"date1 > '2023-06-10': {date1 > '2023-06-10'}\")\n",
    "print(f\"date1 < '2023-06-20': {date1 < '2023-06-20'}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 组合条件筛选\n",
    "\n",
    "当需要同时满足多个时间条件时，可以使用逻辑运算符：\n",
    "\n",
    "**逻辑运算符：**\n",
    "- `&`: 与（and），注意要用括号包裹每个条件\n",
    "- `|`: 或（or）\n",
    "- `~`: 非（not）\n",
    "\n",
    "**常见组合场景：**\n",
    "- 工作日的特定日期范围\n",
    "- 特定季度的周末\n",
    "- 月初或月末的特定星期几\n",
    "- 节假日除外的工作日\n",
    "\n",
    "**注意事项：**\n",
    "- 布尔运算必须使用`&`、`|`、`~`，不能使用Python的`and`、`or`、`not`\n",
    "- 每个条件必须用括号包裹，否则会因运算符优先级出错\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 时间序列比较\n",
    "dates = pd.date_range('2023-01-01', periods=10, freq='D')\n",
    "ts = pd.Series(np.random.randn(10), index=dates)\n",
    "\n",
    "print(\"时间序列:\")\n",
    "print(ts)\n",
    "\n",
    "# 比较特定日期\n",
    "cutoff_date = pd.Timestamp('2023-01-05')\n",
    "before_cutoff = ts.index < cutoff_date\n",
    "after_cutoff = ts.index >= cutoff_date\n",
    "\n",
    "print(f\"\\n{cutoff_date}之前的数据:\")\n",
    "print(ts[before_cutoff])\n",
    "\n",
    "print(f\"\\n{cutoff_date}之后的数据:\")\n",
    "print(ts[after_cutoff])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 实际应用场景\n",
    "\n",
    "日期时间运算和比较在实际数据分析中非常常见，典型的应用场景包括：\n",
    "\n",
    "1. **销售数据分析**\n",
    "   - 比较不同时间段的销售表现\n",
    "   - 分析工作日vs周末的销售差异\n",
    "   - 计算同比增长率\n",
    "\n",
    "2. **用户行为分析**\n",
    "   - 分析用户在特定时间段的活跃度\n",
    "   - 识别用户使用高峰期\n",
    "   - 计算用户留存时间\n",
    "\n",
    "3. **财务数据分析**\n",
    "   - 计算到期日期\n",
    "   - 分析季度/年度财务指标\n",
    "   - 计算利息和投资回报期\n",
    "\n",
    "4. **日志分析**\n",
    "   - 筛选特定时间段的日志\n",
    "   - 分析系统高峰期\n",
    "   - 计算事件间隔时间\n",
    "\n",
    "5. **时间序列预测**\n",
    "   - 准备训练数据的时间窗口\n",
    "   - 划分训练集和测试集\n",
    "   - 进行时间序列特征工程\n",
    "\n",
    "下面的示例展示了如何在实际场景中应用这些技巧。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. 日期范围筛选"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 总结与最佳实践\n",
    "\n",
    "**日期时间运算要点：**\n",
    "1. 固定时间间隔用`Timedelta`，日历时间用`DateOffset`\n",
    "2. 时间差相减得到`Timedelta`对象，可用`.days`、`.total_seconds()`等获取具体数值\n",
    "3. 对时间序列索引可直接进行运算和比较\n",
    "\n",
    "**日期筛选技巧：**\n",
    "1. 利用日期时间属性（`.year`、`.month`、`.dayofweek`等）进行筛选\n",
    "2. 使用`between`方法筛选日期范围更简洁\n",
    "3. 复杂条件使用逻辑运算符`&`、`|`、`~`组合，注意加括号\n",
    "4. DataFrame可以使用`query`方法提高可读性\n",
    "\n",
    "**性能优化建议：**\n",
    "1. 对于大数据集，直接布尔索引比`query`可能更快\n",
    "2. 如果频繁筛选，考虑将日期时间设置为索引\n",
    "3. 使用`DatetimeIndex`的索引切片比布尔筛选更高效\n",
    "\n",
    "**常见陷阱：**\n",
    "1. 布尔运算必须用`&`、`|`，不能用`and`、`or`\n",
    "2. 每个条件必须用括号包裹\n",
    "3. 月末计算需要使用`to_period().end_time`\n",
    "4. 时区问题：注意是否有时区信息，避免比较错误\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建更长的时间序列\n",
    "dates = pd.date_range('2023-01-01', '2023-12-31', freq='D')\n",
    "values = np.random.randn(len(dates)).cumsum()\n",
    "ts = pd.Series(values, index=dates)\n",
    "\n",
    "print(f\"完整时间序列长度: {len(ts)}\")\n",
    "\n",
    "# 筛选特定月份\n",
    "january_data = ts[(ts.index.month == 1)]\n",
    "print(f\"\\n1月份数据点数: {len(january_data)}\")\n",
    "print(january_data.head())\n",
    "\n",
    "# 筛选特定季度\n",
    "q1_data = ts[(ts.index.month >= 1) & (ts.index.month <= 3)]\n",
    "print(f\"\\n第一季度数据点数: {len(q1_data)}\")\n",
    "\n",
    "# 筛选工作日\n",
    "weekday_data = ts[ts.index.dayofweek < 5]  # 0-6, 周一到周日\n",
    "print(f\"\\n工作日数据点数: {len(weekday_data)}\")\n",
    "\n",
    "# 筛选周末\n",
    "weekend_data = ts[ts.index.dayofweek >= 5]\n",
    "print(f\"周末数据点数: {len(weekend_data)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用between方法筛选日期范围\n",
    "start_date = '2023-03-01'\n",
    "end_date = '2023-05-31'\n",
    "\n",
    "spring_data = ts[ts.index.to_series().between(start_date, end_date)]\n",
    "print(f\"春季数据({start_date}到{end_date}):\")\n",
    "print(f\"数据点数: {len(spring_data)}\")\n",
    "print(spring_data.head())\n",
    "\n",
    "# 使用query方法（对DataFrame）\n",
    "df = pd.DataFrame({'value': values, 'date': dates})\n",
    "df.set_index('date', inplace=True)\n",
    "\n",
    "summer_data = df.query(\"index >= '2023-06-01' and index <= '2023-08-31'\")\n",
    "print(f\"\\n夏季数据:\")\n",
    "print(f\"数据点数: {len(summer_data)}\")\n",
    "print(summer_data.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. 复杂的时间条件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 筛选特定时间模式\n",
    "# 每月的15号\n",
    "mid_month = ts[ts.index.day == 15]\n",
    "print(\"每月15号的数据:\")\n",
    "print(mid_month)\n",
    "\n",
    "# 每周五\n",
    "fridays = ts[ts.index.dayofweek == 4]  # 4 = 周五\n",
    "print(f\"\\n周五数据点数: {len(fridays)}\")\n",
    "print(fridays.head())\n",
    "\n",
    "# 月末最后一天\n",
    "month_ends = ts[ts.index == ts.index.to_period('M').end_time.normalize()]\n",
    "print(f\"\\n月末数据点数: {len(month_ends)}\")\n",
    "print(month_ends)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 组合条件筛选\n",
    "# 工作日且是每月前10天\n",
    "complex_condition = (\n",
    "    (ts.index.dayofweek < 5) &  # 工作日\n",
    "    (ts.index.day <= 10)        # 每月前10天\n",
    ")\n",
    "filtered_data = ts[complex_condition]\n",
    "print(\"工作日且每月前10天的数据:\")\n",
    "print(f\"数据点数: {len(filtered_data)}\")\n",
    "print(filtered_data.head(10))\n",
    "\n",
    "# 特定季度的周末\n",
    "q2_weekends = ts[\n",
    "    (ts.index.quarter == 2) &   # 第二季度\n",
    "    (ts.index.dayofweek >= 5)   # 周末\n",
    "]\n",
    "print(f\"\\n第二季度周末数据点数: {len(q2_weekends)}\")\n",
    "print(q2_weekends.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. 实际应用示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 模拟销售数据分析\n",
    "np.random.seed(42)\n",
    "dates = pd.date_range('2023-01-01', '2023-12-31', freq='D')\n",
    "\n",
    "# 模拟销售数据（周末销量更高）\n",
    "base_sales = 1000\n",
    "weekend_boost = np.where(pd.Series(dates).dt.dayofweek >= 5, 500, 0)\n",
    "seasonal_effect = 200 * np.sin(2 * np.pi * np.arange(len(dates)) / 365)\n",
    "noise = np.random.normal(0, 100, len(dates))\n",
    "\n",
    "sales = base_sales + weekend_boost + seasonal_effect + noise\n",
    "sales_df = pd.DataFrame({'sales': sales, 'date': dates})\n",
    "sales_df.set_index('date', inplace=True)\n",
    "\n",
    "print(\"销售数据统计:\")\n",
    "print(sales_df.describe())\n",
    "\n",
    "# 比较工作日和周末销量\n",
    "weekday_sales = sales_df[sales_df.index.dayofweek < 5]['sales']\n",
    "weekend_sales = sales_df[sales_df.index.dayofweek >= 5]['sales']\n",
    "\n",
    "print(f\"\\n工作日平均销量: {weekday_sales.mean():.2f}\")\n",
    "print(f\"周末平均销量: {weekend_sales.mean():.2f}\")\n",
    "print(f\"周末销量提升: {(weekend_sales.mean() / weekday_sales.mean() - 1) * 100:.1f}%\")\n",
    "\n",
    "# 找出销量最高的日期\n",
    "top_sales_days = sales_df.nlargest(5, 'sales')\n",
    "print(\"\\n销量最高的5天:\")\n",
    "print(top_sales_days)\n",
    "\n",
    "# 计算同比增长（假设有去年数据）\n",
    "# 这里模拟去年同期数据\n",
    "last_year_dates = dates - pd.DateOffset(years=1)\n",
    "last_year_sales = sales * 0.9 + np.random.normal(0, 50, len(sales))  # 去年销量稍低\n",
    "\n",
    "# 计算特定日期的同比增长\n",
    "compare_date = pd.Timestamp('2023-06-15')\n",
    "current_sales = sales_df.loc[compare_date, 'sales']\n",
    "last_year_date = compare_date - pd.DateOffset(years=1)\n",
    "last_year_sale = last_year_sales[dates.get_loc(compare_date)]\n",
    "\n",
    "growth_rate = (current_sales - last_year_sale) / last_year_sale * 100\n",
    "print(f\"\\n{compare_date.date()}销量同比增长: {growth_rate:.1f}%\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.8.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
